import { Matrix4, Vector2, Vector3, Quaternion, Spherical } from 'three';

const defAttr = () => ({
    camera: null,//*
    canvas: null,//*
    pvMatrix: null,//*
    rendEvent: null,//如果不传入,着需要外部手动触发
    IsMoveX: true,
    IsMoveY: true,
    eye: new Vector3(0, 0, 3),//*
    lookAt: new Vector3(0, 0, 0),//*

    orZoom: false,//正交缩放
    minZoom: 0.4,
    maxZoom: 15,

    MoveEvent: true,
    RotateEvent: true,
    ScaleEvent: true,

    MoveAction: [],
    RotateAction: [],
    ScaleAction: [],
    setPvM4Action: [],
});

export default class CameraController {
    constructor(data) {
        Object.assign(this, defAttr(), data);

        this.state = 'none';
        this.dragCur = new Vector2();
        this.dragLast = new Vector2();
        this.quaternion = new Quaternion();
        this.spherical = new Spherical();
        this.zoomScale = 0.05;//缩放量
        this.mouseButtons = new Map([
            [0, 'left'],
            [2, 'right'],
        ]);

        this.init();
    }

    init() {
        const { canvas, dragCur, dragLast, orZoom, camera: { type } } = this;
        const { MoveEvent, RotateEvent, ScaleEvent } = this;
        const { MoveAction, RotateAction } = this;
        this.setPvMatrix();

        //取消浏览器右键菜单
        canvas.addEventListener('contextmenu', (event) => {
            event.preventDefault();
        });
        canvas.addEventListener('pointerdown', (event) => {
            const { dragCur, dragLast, mouseButtons } = this;
            dragCur.set(event.clientX, event.clientY);
            dragLast.copy(dragCur);
            this.state = mouseButtons.get(event.button);

        });
        canvas.addEventListener('pointermove', ({ clientX, clientY }) => {
            dragCur.set(clientX, clientY);
            switch (this.state) {
                case 'right':
                    MoveEvent && this[`${type}Position`](dragCur.clone().sub(dragLast));
                    for (let action of MoveAction)
                        action();
                    break;
                case 'left':
                    RotateEvent && this[`${type}Rotation`](dragCur.clone().sub(dragLast));
                    for (let action of RotateAction)
                        action();
                    break;
            }
            dragLast.copy(dragCur);

        });
        canvas.addEventListener('pointerup', (event) => {
            this.state = 'none';
        });
        canvas.addEventListener('wheel', ({ deltaY }) => {
            const { ScaleAction } = this;
            // ScaleEvent && this[`${type}Zoom`](deltaY);
            if (ScaleEvent) {
                if (orZoom == true) {
                    this.OrthographicCameraZoom(deltaY);
                } else {
                    this[`${type}Zoom`](deltaY);
                }
            }
            for (let action of ScaleAction)
                action();
        });

    }

    setEye(v3) {
        this.eye.copy(v3);
        this.setPvMatrix();
    }

    setLookAt(v3) {
        this.lookAt.copy(v3);
        this.setPvMatrix();
    }

    //#region 旋转  记得把方位倾斜角也补上
    PerspectiveCameraRotation(moveDiff) {
        const { fov, aspect } = this.camera;
        const { eye, lookAt } = this;

        const jl = eye.distanceTo(lookAt);

        const cameraH = Math.tan(fov / 2) * jl * 2;
        const cameraW = aspect * cameraH;

        // this.CameraRotation(cameraH, cameraW, moveDiff);
        this.CameraRotationBySpherical(moveDiff);
    }

    OrthographicCameraRotation(moveDiff) {
        const { right, left, top, bottom, } = this.camera;

        const cameraW = right - left;
        const cameraH = top - bottom;

        // this.CameraRotation(cameraH, cameraW, moveDiff);
        this.CameraRotationBySpherical(moveDiff);
    }

    CameraRotation(cameraH, cameraW, moveDiff) {
        const { position, matrix } = this.camera;
        const { clientWidth, clientHeight } = this.canvas;
        const { eye, lookAt, quaternion } = this;

        //计算视图偏移量
        const ratioX = moveDiff.x / clientWidth;
        const ratioY = -moveDiff.y / clientHeight;

        const ratioXBaseHeight = moveDiff.x / clientHeight;
        //位移量
        const ratioLen = new Vector2(ratioXBaseHeight, ratioY).length();
        //旋转量
        const angle = ratioLen * Math.PI * 2;

        //在相机世界中的位移距离
        const distanceLeft = ratioX * cameraW;
        const distanceUp = ratioY * cameraH;

        // 相机本地坐标系的x,y轴
        const mx = new Vector3().setFromMatrixColumn(matrix, 0);
        const my = new Vector3().setFromMatrixColumn(matrix, 1);

        // 将鼠标在相机世界的x,y轴向的位移量转换为世界坐标位
        const vx = mx.clone().multiplyScalar(distanceLeft);
        const vy = my.clone().multiplyScalar(distanceUp);

        //鼠标在世界坐标系中的位移方向-x轴
        const moveDir = vx.clone().add(vy).normalize();

        //目标点到视点的单位向量-z轴
        const eyeDir = position.clone().sub(lookAt).normalize();

        //基于位移方向和视线获取旋转轴-上方向y轴
        const axis = moveDir.clone().cross(eyeDir);
        quaternion.setFromAxisAngle(axis, angle);

        this.updateRotation(quaternion);
    }

    CameraRotationBySpherical(moveDiff) {
        const { spherical, canvas } = this;
        const { clientHeight } = canvas;
        const pi2 = Math.PI * 2;

        moveDiff.x = moveDiff.x / this.camera.zoom;
        moveDiff.y = moveDiff.y / this.camera.zoom;

        spherical.theta -= pi2 * moveDiff.x / clientHeight;
        //倾斜角[上下旋转是有问题的,不能超过180度]
        // spherical.phi -= pi2 * moveDiff.y / clientHeight;

        //X最大旋转角
        // const maxX = Math.PI / 2;
        // if (spherical.theta > maxX) {
        //     spherical.theta = maxX;
        // }
        // if (spherical.theta < -maxX) {
        //     spherical.theta = -maxX;
        // }

        //Y最大旋转角 180度
        spherical.phi = Math.min(
            Math.PI * 0.9999999,
            Math.max(0.0000001, spherical.phi - (pi2 * moveDiff.y / clientHeight))
        );

        this.updateRotationBySpherical(spherical);
    }

    updateRotation(quaternion) {
        const { camera, eye, lookAt } = this;
        const rotateOffset = camera.position.clone()
            .sub(lookAt)
            .applyQuaternion(quaternion)

        eye.copy(lookAt.clone().add(rotateOffset));
        camera.up.applyQuaternion(quaternion)

        this.setPvMatrix();
    }

    updateRotationBySpherical(spherical) {
        const { eye, lookAt } = this;
        const rotateOffset = new Vector3().setFromSpherical(spherical);

        eye.copy(lookAt.clone().add(rotateOffset));

        this.setPvMatrix();
    }
    //#endregion

    //#region 移动
    PerspectiveCameraPosition(moveDiff) {
        const { eye, lookAt ,canvas} = this;
        const { matrix, up, fov, aspect } = this.camera;
        //两点的距离
        let len = eye.distanceTo(lookAt);
        //获取摄像机弧度的一半
        let halfFov = fov * Math.PI / 360;
        //获取视点的高度
        let sh = len * Math.tan(halfFov) * 2;
        //获取视点的宽度
        let sw = aspect * sh;

        let x = moveDiff.x * sw / canvas.clientWidth;
        let y = moveDiff.y * sh / canvas.clientHeight;
        // updatePosition(new Vector3(-x, y, 0));//这个是基于世界坐标移动

        //因为Z轴不知道,所以需要进行分开相乘

        //获取相机本地坐标系中的X列
        const cameraX = new Vector3().setFromMatrixColumn(matrix, 0)
        const myOrz = new Vector3();

        // //根据上方向与X轴进行叉乘,获取-z轴
        // myOrz.crossVectors(this.camera.up, cameraX);

        //获取相机的Y轴
        myOrz.setFromMatrixColumn(matrix, 1);

        const vx = cameraX.clone().multiplyScalar(-x);
        const vy = myOrz.clone().multiplyScalar(y);

        this.updatePosition(vx.add(vy));
    }

    OrthographicCameraPosition(moveDiff) {
        const { right, left, top, bottom, matrix } = this.camera;
        const { clientWidth, clientHeight } = this.canvas;
        //相机的宽
        const cameraW = right - left;
        //相机的高
        const cameraH = top - bottom;

        //计算视图偏移量
        const ratioX = moveDiff.x / clientWidth;
        const ratioY = moveDiff.y / clientHeight;

        //将视图偏移量映射到相机偏移量
        const distanceLeft = ratioX * cameraW;
        const distanceUp = ratioY * cameraH;

        //拿到相机本地坐标系的X轴
        const mx = new Vector3().setFromMatrixColumn(matrix, 0);
        const my = new Vector3().setFromMatrixColumn(matrix, 1);

        //计算视图偏移量
        const vx = mx.multiplyScalar(-distanceLeft);
        const vy = my.multiplyScalar(distanceUp);

        this.updatePosition(vx.add(vy));
    }

    updatePosition(vec3) {
        const { eye, lookAt } = this;
        eye.add(vec3);
        lookAt.add(vec3);
        this.setPvMatrix();
    }
    //#endregion

    //#region 缩放
    PerspectiveCameraZoom(zoomDiff) {
        const { zoomScale } = this;
        //#region lerp缩放
        // if (zoomDiff < 0) {
        //     this.PerspectiveCameraUpdateZoom(zoomScale);
        // } else {
        //     this.PerspectiveCameraUpdateZoom(-zoomScale);
        // }
        //#endregion

        //#region 球坐标缩放
        if (zoomDiff < 0) {
            this.PerspectiveCameraUpdateZoom(1 - zoomScale);
        } else {
            this.PerspectiveCameraUpdateZoom(1 + zoomScale);
        }
        //#endregion
    }

    PerspectiveCameraUpdateZoom(scale) {
        const { camera, spherical, lookAt, eye } = this;
        //#region lerp缩放
        // eye.lerp(lookAt, scale);
        //#endregion

        //#region 球坐标缩放
        spherical.setFromVector3(eye.clone().sub(lookAt));
        spherical.radius *= scale;
        const rotateOffset = new Vector3().setFromSpherical(spherical);
        eye.copy(lookAt.clone().add(rotateOffset))
        //#endregion
        this.setPvMatrix();
    }

    OrthographicCameraZoom(zoomDiff) {
        const { zoomScale } = this;
        if (zoomDiff < 0) {
            this.OrthographicCameraUpdateZoom(1 + zoomScale);
        } else {
            this.OrthographicCameraUpdateZoom(1 - zoomScale);
        }
    }

    OrthographicCameraUpdateZoom(scale) {
        const { minZoom, maxZoom } = this;
        let zoom = this.camera.zoom * scale;
        zoom = Math.min(maxZoom, Math.max(minZoom, zoom));
        this.camera.zoom = zoom;
        this.setPvMatrix();
    }

    //#endregion

    setPvMatrix() {
        const { eye, lookAt, camera, pvMatrix, rendEvent, spherical } = this;
        camera.position.copy(eye);
        camera.lookAt(lookAt);
        camera.updateProjectionMatrix(true);
        camera.updateWorldMatrix(true);
        pvMatrix.multiplyMatrices(
            camera.projectionMatrix,
            camera.matrixWorldInverse
        );

        spherical.setFromVector3(
            camera.position.clone().sub(lookAt)
        );
        if (rendEvent != null) {
            rendEvent();
        }

        for (let action of this.setPvM4Action)
            action();
    }
}
